Imports System.IO
Imports System.Drawing
Imports System.Drawing.Imaging
Imports System.ServiceModel
Imports System.ServiceModel.PeerResolvers
Imports System.ServiceModel.Dispatcher

' WCF service contract that defines how messages are sent/received
' to/from the Peer-to-Peer network.
<ServiceContract()> _
Friend Interface IPictureSharer
    ' Note: P2P communication requries the one way operation contract.
    <OperationContract(IsOneWay:=True)> _
    Sub SharePicture(ByVal message As PictureShareMessage)
End Interface

' WCF message contract that defines the message structure used.
<MessageContract()> _
Friend Class PictureShareMessage
    ' Picture filename.
    <MessageHeader()> _
    Private _fileName As String

    ' Picture stream data.
    <MessageBodyMember()> _
    Private _pictureStream As Stream

    Public ReadOnly Property PictureName() As String
        Get
            Return Me._fileName
        End Get
    End Property

    Public ReadOnly Property PictureStream() As Stream
        Get
            Return Me._pictureStream
        End Get
    End Property

    Public Sub New()
    End Sub

    Public Sub New(ByVal fileName As String, ByVal pictureStream As Stream)
        If fileName Is Nothing Then
            Throw New ArgumentNullException("fileName")
        End If
        If pictureStream Is Nothing Then
            Throw New ArgumentNullException("pictureStream")
        End If

        Me._fileName = fileName
        Me._pictureStream = pictureStream
    End Sub
End Class

' Defines the peer channel behavior and hosts the service endpoint
' so that picture sharing messages can be sent and received.
' Note: we must implement the IPictureSharer service contract.
<ServiceBehavior(InstanceContextMode:=InstanceContextMode.Single)> _
Friend Class PictureSharePeerChannel
    Implements IPictureSharer
    ' Define full implementation requirements for the peer channel
    ' that we are creating.
    Private Interface IPictureViewerClient
        Inherits IPictureSharer, IClientChannel
    End Interface

    ' Will generate the picture sharing peer channel.
    Private _channelFactory As ChannelFactory(Of IPictureViewerClient) = Nothing

    ' The generated picture sharing peer channel will be the type
    ' defined by the service contract, IPictureSharer.
    Private _channel As IPictureSharer = Nothing

    ' P2P address (e.g. net.p2p://PictureShare)
    Private _address As EndpointAddress = Nothing

    ' We host an instance of the WCF service for the picture sharing
    ' P2P application.
    Private _host As ServiceHost = Nothing

    ' Our picture cache manager.
    Private _cacheMgr As PictureShareCacheManager = Nothing


    Public Sub New()
        ' Setup picture cache manager.
        Me._cacheMgr = New PictureShareCacheManager(SettingsWrapper.CacheFolder)

        ' Setup peer channel connection.
        Connect()
    End Sub

#Region "IPictureSharer Members"

    ' Implementation of service contract.
    ' Note: This method is called when a message is RECEIVED
    ' from the P2P network. See SendPicture method for example
    ' of how a message is SENT.
    Public Sub SharePicture(ByVal message As PictureShareMessage) Implements IPictureSharer.SharePicture
        ' Use our cache manager to save the message contents.
        Me._cacheMgr.Save(message.PictureStream, message.PictureName)
    End Sub

#End Region


    Public Sub Connect()
        ' Construct the full P2P network mesh address.
        ' Note: all P2P addresses start with "net.p2p://" for the protocol.
        Dim meshAddress As Uri = New Uri("net.p2p://" & SettingsWrapper.P2PAddress)

        ' Define the binding used for P2P.
        Dim binding As NetPeerTcpBinding = New NetPeerTcpBinding()
        ' Using Peer Name Resolution Protocol (PNRP) to find other instances
        ' of the picture sharing application.
        binding.Resolver.Mode = PeerResolverMode.Pnrp
        ' Using plain text password for simplicity.
        binding.Security.Transport.CredentialType = PeerTransportCredentialType.Password
        ' Set the maximum size of a message in bytes.
        binding.MaxReceivedMessageSize = 1000000L
        binding.ReaderQuotas.MaxDepth = Integer.MaxValue
        binding.ReaderQuotas.MaxStringContentLength = Integer.MaxValue
        binding.ReaderQuotas.MaxArrayLength = Integer.MaxValue
        binding.ReaderQuotas.MaxBytesPerRead = Integer.MaxValue
        binding.ReaderQuotas.MaxNameTableCharCount = Integer.MaxValue
        binding.Security.Mode = SecurityMode.None

        ' Setup and define a WCF service host.
        Me._host = New ServiceHost(Me)
        Me._host.AddServiceEndpoint(GetType(IPictureSharer), binding, meshAddress)
        Me._host.Credentials.Peer.MeshPassword = SettingsWrapper.P2PPassword
        Me._host.Open()

        ' Create the P2P channel using a channel factory.
        Me._address = New EndpointAddress(meshAddress)
        Me._channelFactory = New ChannelFactory(Of IPictureViewerClient)(binding, Me._address)
        Me._channelFactory.Credentials.Peer.MeshPassword = SettingsWrapper.P2PPassword
        Me._channel = Me._channelFactory.CreateChannel()
    End Sub

    Public Sub SendPicture(ByVal fileName As String)
        ' Perform some checks on the provided filename.
        If fileName Is Nothing Then
            Throw New ArgumentNullException("fileName")
        End If
        If (Not Path.IsPathRooted(fileName)) Then
            Throw New ArgumentException("File does not include full path.", "fileName")
        End If
        If (Not File.Exists(fileName)) Then
            Throw New ArgumentException("File not found.", "fileName")
        End If

        ' Stream that will be used to save picture information into.
        Dim picStream As MemoryStream = New MemoryStream()

        Try
            ' Load the picture filename into an Image object.
            Using origPic As Image = New System.Drawing.Bitmap(fileName)
                ' MaxSize represents the largest pixel count that is allowed
                ' in any direction (width or height).
                Dim MaxSize As UInteger = SettingsWrapper.MaxPictureSize

                ' Image will be shrunk if needed, maintaining aspect ratio.
                Dim finalPic As Image = ShrinkImage(origPic)

                ' Save picture bits to the memory stream.
                finalPic.Save(picStream, ImageFormat.Jpeg)
                finalPic.Dispose()
            End Using
            ' If the file is not a valid image, we ignore it.
        Catch e1 As ArgumentException
            Return
        End Try

        ' Reset the stream position to the beginning so that all
        ' P2P nodes that receive the message can simply read to the end of the
        ' stream to get the picture data.
        picStream.Position = 0

        ' Use our P2P channel to broadcast the message to all listening nodes.
        Me._channel.SharePicture(New PictureShareMessage(Path.GetFileNameWithoutExtension(fileName), picStream))
    End Sub

    ' Shrinks a given image so that the largest side (width or height)
    ' is no larger than a configured maximum pixel value. Images that already
    ' fit within this requirement are not altered.
    Private Function ShrinkImage(ByVal img As Image) As Image
        Dim shrinkPercentage As Double = 0
        Dim finalPic As Image = img
        Dim MaxSize As UInteger = SettingsWrapper.MaxPictureSize

        If img.Width >= img.Height AndAlso img.Width > MaxSize Then
            shrinkPercentage = CDbl(MaxSize) / CDbl(img.Width)
        ElseIf img.Height >= img.Width AndAlso img.Height > MaxSize Then
            shrinkPercentage = CDbl(MaxSize) / CDbl(img.Height)
        End If

        If shrinkPercentage <> 0 Then
            Dim newWidth As Integer = CInt(Fix(img.Width * shrinkPercentage))
            Dim newHeight As Integer = CInt(Fix(img.Height * shrinkPercentage))
            finalPic = New System.Drawing.Bitmap(img, newWidth, newHeight)
        End If

        Return finalPic
    End Function

    Public Sub Close()
        Me._host.Close()
    End Sub

End Class
